package ualrcdg.shared.dto;

import java.util.Arrays;

import ualrcdg.shared.exceptions.CDGException;

/* LATER On the use of unmodifiable collections, and these copy constructors:
 * http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf, slide 75
 */

/**
 * Static description of a Finite State Machine with lookups implemented using a transition matrix.
 * Transitions are recorded (and hence retrieved) in the following manner:  
 *   Arbitrary state numbers (0...n-1) are used as indices within a two-dimensional array of (TransitionType) object.
 *   Each row of this transition matrix represents the transitions available from a given origin state, and (implicitly) the transition that must be undergone to arrive at a desired destination state.
 * 
 * @param <TransitionType> data type used to represent transitions in the represented FSM's transition matrix 
 */
public class FSM_TransitionMatrix<TransitionType>
{
	protected final int stateCount;
	protected final int initialState;
	protected final int[] acceptingStates;
	protected final TransitionType[][] transitionMatrix;
	
	/**
	 * Primary constructor
	 *
	 * @param stateCount the stateCount of the FSM_TransitionMatrix object to be created
	 * @param initialState the initialState of the FSM_TransitionMatrix object to be created
	 * @param acceptingStates the acceptingStates of the FSM_TransitionMatrix object to be created
	 * @param transitionMatrix the transitionMatrix of the FSM_TransitionMatrix object to be created
	 * @throws CDGException If the transition matrix description of the FSM is invalid
	 */
	@SuppressWarnings("unchecked")
	protected FSM_TransitionMatrix( int stateCount, int initialState, int[] acceptingStates, TransitionType[][] transitionMatrix ) throws CDGException
	{
		this.stateCount = stateCount;
	
		if( !isValidState( initialState ) )
		{
			throw new CDGException( "Unable to create FSM: Invalid initial state: " + initialState + " in FSM with stateCount = " + stateCount );
		}
		this.initialState = initialState;
		
		if( acceptingStates == null || acceptingStates.length == 0 )
		{
			throw new CDGException( "Unable to create FSM: must have one or more accepting states." );
		}

		this.acceptingStates = new int[acceptingStates.length];
		
		for( int i = 0; i < acceptingStates.length; i++ )
		{
			if( !isValidState( acceptingStates[i] ) )
			{
				throw new CDGException( "Unable to create FSM: Invalid accepting state: " + acceptingStates[i] + " in FSM with stateCount = " + stateCount );
			}
			this.acceptingStates[i] = acceptingStates[i]; 
		}
		
		if( transitionMatrix == null )
		{
			throw new CDGException( "Unable to create FSM: transition matrix must be provided." );
		}
		
		if( transitionMatrix.length != stateCount )
		{
			throw new CDGException( "Unable to craete FSM: transition matrix must have length = stateCount." );
		}

		this.transitionMatrix = ( TransitionType[ ][ ] ) new Object[transitionMatrix.length][];
		
		for( int i = 0; i < transitionMatrix.length; i++ )
		{
			this.transitionMatrix[i] = ( TransitionType[ ] ) new Object[transitionMatrix[i].length];
			
			if( transitionMatrix[i] == null )
			{
				throw new CDGException( "Unable to create FSM: transition matrix cannot have null rows." );
			}
			
			if( transitionMatrix[i].length != stateCount )
			{
				throw new CDGException( "Unable to create FSM: transition matrix rows must have length = stateCount." );
			}
			
			for( int j = 0; j < transitionMatrix[i].length; j++ )
			{
				this.transitionMatrix[i][j] = transitionMatrix[i][j]; 
			}
		}
		//LATER Ensure that all states can reach accepting state. 
	}
	
	/*
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString( )
	{
		return "FSM [stateCount=" + stateCount + ", initialState=" + initialState + ", acceptingStates=" + Arrays.toString( acceptingStates ) + ", transitionMatrix=" + Arrays.deepToString( transitionMatrix ) + "]";
	}

	/**
	 * Determines whether a particular state index is valid for this FSM instance
	 * 
	 * @param state the state index to be validated
	 * @return true if the state index is valid (that is, if a state with such an index exists in this FSM instance), false otherwise
	 */
	private boolean isValidState( int state )
	{
		return ( state >= 0 && state < stateCount );
	}
	
	/**
	 * @return the stateCount of this FSM_TransitionMatrix instance
	 */
	public int getStateCount( )
	{
		return stateCount;
	}
	
	/**
	 * @return the initialState of this FSM_TransitionMatrix instance
	 */
	public int getInitialState( )
	{
		return initialState;
	}
	
	/**
	 * Determines whether a particular state is an accepting state on this FSM instance 
	 * 
	 * @param currentState the state index to check
	 * @return true if the currentState is an accepting state, false otherwise
	 */
	public boolean isAcceptingState( int currentState )
	{
		for( int i = 0; i < acceptingStates.length; i++ )
		{
			if( acceptingStates[i] == currentState )
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Retrieves a row from this FSM instance's transition matrix
	 * 
	 * @param currentState the state whose transitions (and implied destinations) are to be fetched
	 * @return the transition row for currentState
	 */
	public TransitionType[ ] getTransitionRow( int currentState )
	{
		return (TransitionType[])Arrays.copyOf( transitionMatrix[currentState], transitionMatrix[currentState].length );
	}
}
